// Copyright (c) 2023 Presto Labs Pte. Ltd.
// Author: tianqiang

#pragma once

#include <sstream>

#include <boost/uuid/uuid_generators.hpp>
#include <boost/uuid/uuid_io.hpp>

#include "coin2/exchange/base/api_util/api_signature.h"

class BittrexWsAuth {
 public:
  explicit BittrexWsAuth(const std::string& key_filepath) : key_(AuthKey::FromFile(key_filepath)) {}

  const std::string CreateSignature() const {
    int64_t timestamp = GetCurrentTimestampMs();
    auto uuid = boost::uuids::to_string(boost::uuids::random_generator()());

    std::string json = R"({{"H":"c3","M":"Authenticate","A":["{}",{},"{}","{}"],"I":0}})";

    std::string signature_content = std::to_string(timestamp) + uuid;

    std::string signature =
        ApiSignatureUtil::GetSignatureByHexHmacSHA512(signature_content, key_.secret_key());

    return fmt::format(json, key_.access_key(), timestamp, uuid, signature);
  }

 protected:
  AuthKey key_;
};

class BittrexRestAuth : public BaseRestAuth {
 public:
  explicit BittrexRestAuth(
      const std::string& key_filepath,
      const std::string& host,
      const std::string& port,
      const std::string& bind_ip)
      : key_(AuthKey::FromFile(key_filepath)), host_(host), port_(port), bind_ip_(bind_ip) {
    header_.emplace_back("Content-Type", "application/json");
    header_.emplace_back("Api-Key", key_.access_key());
    header_.emplace_back("Api-Timestamp", "");
    header_.emplace_back("Api-Content-Hash", "");
    header_.emplace_back("Api-Signature", "");
  }

  explicit BittrexRestAuth(
      const std::string& key_filepath,
      const std::string& host,
      int32_t port,
      const std::string& bind_ip)
      : BittrexRestAuth(key_filepath, host, std::to_string(port), bind_ip) {}

  AsioHttpContext* CreateRequestByGetWithSignature(
      const std::string& url_path,
      UrlParamsBuilder* builder) override {
    return CreateRequestByGetWithSignature(url_path, builder, host_);
  }

  AsioHttpContext* CreateRequestByGetWithSignature(
      const std::string& url_path,
      UrlParamsBuilder* builder,
      const std::string& host) {
    std::string query_string = builder->ComputeQueryString();
    std::string post_body = builder->ComputePostBody();
    CHECK(post_body.empty());

    std::string target = url_path;
    if (!query_string.empty()) {
      target += "?" + query_string;
    }

    GenSignature(target, "GET", post_body);

    return AsioHttpContext::NewGet(host, port_, bind_ip_, target, header_);
  }

  AsioHttpContext* CreateRequestByPostWithSignature(
      const std::string& url_path,
      UrlParamsBuilder* builder) override {
    std::string query_string = builder->ComputeQueryString();
    std::string post_body = builder->ComputePostBody();
    CHECK(query_string.empty());

    GenSignature(url_path, "POST", post_body);

    return AsioHttpContext::NewPost(host_, port_, bind_ip_, url_path, header_, post_body);
  }

  AsioHttpContext* CreateRequestByDeleteWithSignature(
      const std::string& url_path,
      UrlParamsBuilder* builder) override {
    std::string query_string = builder->ComputeQueryString();
    std::string post_body = builder->ComputePostBody();
    CHECK(post_body.empty());

    std::string target = url_path;
    if (!query_string.empty()) {
      target += "?" + query_string;
    }

    GenSignature(target, "DELETE", post_body);

    return AsioHttpContext::NewDelete(host_, port_, bind_ip_, target, header_);
  }

  AsioHttpContext* CreateRequestByPutWithSignature(
      const std::string& address,
      UrlParamsBuilder* builder) override {
    return nullptr;
  }

 protected:
  AuthKey key_;
  std::string host_;
  std::string port_;
  std::string bind_ip_;
  std::vector<std::pair<std::string, std::string>> header_;

 private:
  std::string GetSignature(
      const std::string& timestamp,
      const std::string& uri,
      const std::string& method,
      const std::string& content_hash) {
    std::stringstream ss;

    ss << timestamp << "https://" << host_ << uri << method << content_hash;

    auto pre_sign = ss.str();

    auto sign = ApiSignatureUtil::GetSignatureByHexHmacSHA512(pre_sign, key_.secret_key());

    return sign;
  }

  void GenSignature(const std::string& uri, const std::string& method, const std::string& body) {
    auto timestamp = std::to_string(GetCurrentTimestampMs());
    auto content_hash = ApiSignatureUtil::GetHashByHexSHA512(body);
    auto sign = GetSignature(timestamp, uri, method, content_hash);

    header_[2].second = timestamp;
    header_[3].second = content_hash;
    header_[4].second = sign;
  }
};
